package com.mf.web.framework.realm;

import com.mf.common.sso.LoginContext;
import com.mf.common.sso.LoginUser;
import com.mf.constant.MFConstant;
import com.mf.domain.authority.RoleResourceRelation;
import com.mf.domain.authority.UserInfo;
import com.mf.domain.authority.UserRoleRelation;
import com.mf.service.authority.RoleResourceRelationService;
import com.mf.service.authority.UserInfoService;
import com.mf.service.authority.UserRoleRelationService;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Web用户身份验证及授权服务类
 * User: zhaoming
 * Date: 15-1-16
 * Time: 上午10:14
 * To change this template use File | Settings | File Templates.
 */
public class WebUserRealm extends AuthorizingRealm{

    public Logger logger = LoggerFactory.getLogger(getClass());

    @Resource
    private UserInfoService userInfoService;
    @Resource
    private UserRoleRelationService userRoleRelationService;
    @Resource
    private RoleResourceRelationService roleResourceRelationService;

    /**
     * 验证当前登录的Subject
     * 经测试:本例中该方法的调用时机为LoginController.login()方法中执行Subject.login()时
     */
    public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        AuthenticationInfo authenticationInfo = null;
        //获取基于用户名和密码的令牌
        //实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的
        //两个token的引用都是一样的
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;

        logger.debug("验证当前Subject时获取到token为" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));

        //此处无需比对,比对的逻辑Shiro会做,我们只需返回一个和令牌相关的正确的验证信息
        //说白了就是第一个参数填登录用户名,第二个参数填合法的登录密码(可以是从数据库中取到的,本例中为了演示就硬编码了)
        //这样一来,在随后的登录页面上就只有这里指定的用户和密码才能通过验证
        UserInfo userInfo = new UserInfo();
        userInfo.setUserCode(token.getUsername());
        List<UserInfo> userInfoList = userInfoService.selectByCondition(userInfo);
        if(userInfoList==null || userInfoList.size()==0){
            //用户名不存在
            throw new UnknownAccountException();
        }
        userInfo = userInfoList.get(0);
        logger.info("当前登录人密码："+DigestUtils.md5Hex(String.valueOf(token.getPassword())));
        if(DigestUtils.md5Hex(String.valueOf(token.getPassword())).equals(userInfo.getPassWord())){
            authenticationInfo = new SimpleAuthenticationInfo(token.getUsername(), token.getPassword(), this.getName());
            //保存登陆人信息
            LoginUser loginUser = new LoginUser();
            loginUser.setUserId(userInfo.getId());
            loginUser.setUserPortraitId(userInfo.getUserPortraitId());
            loginUser.setUserCode(userInfo.getUserCode());
            loginUser.setUserName(userInfo.getUserName());
            loginUser.setLoginType(MFConstant.LOGIN_TYPE_WEB);
            LoginContext.setLoginUser(loginUser);

            this.setSession("userId", userInfo.getId());
            this.setSession("userPortraitId", userInfo.getUserPortraitId());
            this.setSession("userCode", userInfo.getUserCode());
            this.setSession("userName", userInfo.getUserName());
            this.setSession("loginType", MFConstant.LOGIN_TYPE_WEB);
        }else{
            //密码错误
            throw new IncorrectCredentialsException();
        }
        return authenticationInfo;
    }

    /**
     * 为当前登录的Subject授予角色和权限
     * 经测试:本例中该方法的调用时机为需授权资源被访问时
     * 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
     * 个人感觉若使用了Spring3.1开始提供的ConcurrentMapCache支持,则可灵活决定是否启用AuthorizationCache
     * 比如说这里从数据库获取权限信息时,先去访问Spring3.1提供的缓存,而不使用Shior提供的AuthorizationCache
     */
    public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){
        logger.debug("开始为用户授权");
        //当前用户的角色列表
        Set<String> roles = new HashSet<String>();
        //当前用户的资源列表
        Set<String> permissions = new HashSet<String>();

        //获取当前登录的用户信息,等价于(String)principals.fromRealm(this.getName()).iterator().next()
        String currentUsername = (String)super.getAvailablePrincipal(principals);
        if(currentUsername==null || currentUsername.equals("")){
            throw new AuthorizationException();
        }

        //获取当前用户的角色列表与资源列表
        UserRoleRelation userRoleRelation = new UserRoleRelation();
        userRoleRelation.setUserCode(currentUsername);
        List<UserRoleRelation> userRoleRelations = userRoleRelationService.selectByCondition(userRoleRelation);
        for(UserRoleRelation userRole : userRoleRelations){
            roles.add(userRole.getRoleCode());
            //获取当前用户的资源列表
            RoleResourceRelation roleResourceRelation = new RoleResourceRelation();
            roleResourceRelation.setRoleCode(userRole.getRoleCode());
            List<RoleResourceRelation> roleResourceRelations =  roleResourceRelationService.selectByCondition(roleResourceRelation);
            for(RoleResourceRelation roleResource : roleResourceRelations){
                permissions.add(roleResource.getResourceCode()+":reload");
                permissions.add(roleResource.getResourceCode()+":delete");
                permissions.add(roleResource.getResourceCode()+":insert");
                permissions.add(roleResource.getResourceCode()+":update");
            }
        }


        //为当前用户设置角色和权限
        SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();
        simpleAuthorInfo.addRoles(roles);
        simpleAuthorInfo.addStringPermissions(permissions);

        for(String role : roles){
            logger.debug("已为用户[ "+currentUsername+" ]赋予了[ "+role+" ]角色");
        }
        logger.debug("----------------------------------------------------------");
        for(String permission : permissions){
            logger.debug("已为用户[ "+currentUsername+" ]赋予了[ "+permission+"：* ]资源");
        }

        logger.debug("为用户授权结束");
//        SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();
//        //实际中可能会像上面注释的那样从数据库取得
//        if(null!=currentUsername && "group".equals(currentUsername)){
//            //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色
//            simpleAuthorInfo.addRole("group");
//            //添加权限
//            simpleAuthorInfo.addStringPermission("USER_INFO:create,update");
//            logger.debug("已为用户[admin]赋予了[admin]角色和[USER_INFO:create,update]权限");
//            return simpleAuthorInfo;
//        }
        return simpleAuthorInfo;
    }


    /**
     * 将一些数据放到ShiroSession中,以便于其它地方使用
     * 比如Controller,使用时直接用HttpSession.getAttribute(key)就可以取到
     */
    private void setSession(Object key, Object value){
        Subject currentUser = SecurityUtils.getSubject();
        if(null != currentUser){
            Session session = currentUser.getSession();
            logger.debug("Session默认超时时间为[" + session.getTimeout() + "]毫秒");
            if(null != session){
                session.setAttribute(key, value);
            }
        }
    }
}
